package com.google.code.mochaccino.framework.crypto.x509;

import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

/** SSL Context Factory */
public final class X509ContextFactory {
	/** The Key Managers */
	private List<X509KeyManager> keyManagers = Collections.synchronizedList( new ArrayList<X509KeyManager>() );

	/** The Trust Managers */
	private List<X509TrustManager> trustManagers = Collections.synchronizedList( new ArrayList<X509TrustManager>() );

	/** The SSL Context */
	private SSLContext context;

	/** Constructor */
	public X509ContextFactory() {
		this( true );
	}

	/** Constructor */
	public X509ContextFactory( boolean addSystem ) {
		if ( addSystem ) {
			addSystemManager();
		}
	}

	/** Add System Managers (Trust & Keys) */
	private void addSystemManager() {
		try {
			addSystemKeyManager();
			addSystemTrustManager();
		} catch ( NoSuchAlgorithmException e ) {
			throw new RuntimeException( e );
		} catch ( KeyStoreException e ) {
			throw new RuntimeException( e );
		} catch ( UnrecoverableKeyException e ) {
			throw new RuntimeException( e );
		}
	}

	/** Add System Key Manager (Platform Dependant) */
	private void addSystemKeyManager() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
		String algorithm = KeyManagerFactory.getDefaultAlgorithm();
		KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( algorithm );
		keyManagerFactory.init( null, null );
		for ( KeyManager manager : keyManagerFactory.getKeyManagers() ) {
			if ( manager instanceof X509KeyManager ) {
				add( (X509KeyManager) manager );
			} else {
				throw new RuntimeException( "Unexpected TrustManager type " + manager.getClass().getName() );
			}
		}
	}

	/** Add KeyManager */
	public void add( X509KeyManager manager ) {
		if ( context != null ) {
			throw new IllegalStateException( "SSL Context is already initialized" );
		}
		keyManagers.add( manager );
	}

	/** Add System Trust Managers (Platform dependant) */
	private void addSystemTrustManager() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
		String algorithm = TrustManagerFactory.getDefaultAlgorithm();
		TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( algorithm );
		trustManagerFactory.init( (KeyStore) null );
		for ( TrustManager manager : trustManagerFactory.getTrustManagers() ) {
			if ( manager instanceof X509TrustManager ) {
				add( (X509TrustManager) manager );
			} else {
				throw new RuntimeException( "Unexpected TrustManager type " + manager.getClass().getName() );
			}
		}
	}

	/** Trust Manager */
	public void add( X509TrustManager manager ) {
		if ( context != null ) {
			throw new IllegalStateException( "SSL Context is already initialized" );
		}
		trustManagers.add( manager );
	}

	/** Return the SSL Context */
	public SSLContext getContext() {
		if ( context == null ) {
			throw new IllegalStateException( "SSL Context is not initialized" );
		}
		return context;
	}

	/** Initialize the Context */
	public void initialize() {
		initialize( "SSL", null );
	}

	/** Initialize the Context */
	public void initialize( String algo ) {
		initialize( algo, null );
	}

	/** Initialize the Context */
	public synchronized void initialize( String algo, Provider provider ) {
		if ( context != null ) {
			throw new IllegalStateException( "SSL Context is already initialized" );
		}

		try {
			context = provider != null ? SSLContext.getInstance( algo, provider ) : SSLContext.getInstance( algo );
			context.init( keyManagers.toArray( new KeyManager[keyManagers.size()] ), trustManagers.toArray( new TrustManager[trustManagers.size()] ), null );
		} catch ( NoSuchAlgorithmException e ) {
			throw new RuntimeException( e );
		} catch ( KeyManagementException e ) {
			throw new RuntimeException( e );
		}
	}
}
